/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package plash.display;

import java.util.ArrayList;
import plash.geom.Point;
import plash.geom.Rectangle;
import plash.utils.Debug;
import plash.utils.Logger;
import processing.core.PGraphics;

/**
 *
 * @author #0K Srinivasan <hashok.com>
 */
public class DisplayObjectContainer extends InteractiveObject
{

    protected boolean __isRoot = false;
    protected ArrayList<DisplayObject> __children;
    protected ArrayList<DisplayObject> __before;
    protected ArrayList<DisplayObject> __after;
    protected Graphics __graphics;

    public DisplayObjectContainer()
    {
        _init();
    }

    private void _init()
    {
        __children = new ArrayList<DisplayObject>();
        __before = new ArrayList<DisplayObject>();
        __after = new ArrayList<DisplayObject>();
        __graphics = new Graphics(this);
    }

    private Rectangle _findRotatedBounds(Rectangle unrotated)
    {
        //the distance between this Display group's registration point and the rectangle's topleft
        int distanceGtoR;
        float fi;
        float theta = this.__radiansRotation;

        Point minPoint = new Point(999999, 999999);
        Point maxPoint = new Point(-999999, -999999);

        ArrayList<Point> rotatedCorners = new ArrayList<Point>();

        distanceGtoR = plash.utils.Math.distance(this.__location, unrotated.getTopLeft());
        fi = (float) java.lang.Math.atan2(
                unrotated.getTopLeft().getY() - __y,
                unrotated.getTopLeft().getX() - __x);

        //rotated Top Left
        rotatedCorners.add(plash.utils.Math.getRotatedLocation(this.__location, distanceGtoR, theta + fi));
        //rotated Top Right
        rotatedCorners.add(plash.utils.Math.getRotatedLocation(rotatedCorners.get(0), unrotated.getWidth(), theta));
        //rotated Bottom Left
        rotatedCorners.add(plash.utils.Math.getRotatedLocation(rotatedCorners.get(0), unrotated.getHeight(), theta + 1.57f));
        //rotated Bottom Right
        rotatedCorners.add(plash.utils.Math.getRotatedLocation(rotatedCorners.get(1),
                unrotated.getHeight(), theta + 1.57f));

        for (Point p : rotatedCorners)
        {
            minPoint.setX(java.lang.Math.min(minPoint.getX(), p.getX()));
            minPoint.setY(java.lang.Math.min(minPoint.getY(), p.getY()));

            maxPoint.setX(java.lang.Math.max(maxPoint.getX(), p.getX()));
            maxPoint.setY(java.lang.Math.max(maxPoint.getY(), p.getY()));
        }
        if (Debug.debug_enabled)
        {
            Logger.log("Distance GTOR : " + distanceGtoR);
            Logger.log("FI :  " + fi);
            Logger.log("THETA : " + theta);
            Logger.log("UNROTATED BOX : " + unrotated);
            Logger.log("MINPOINT : " + minPoint);
            Logger.log("MAXPOINT : " + maxPoint);
        }
        return new Rectangle(minPoint, new Point(maxPoint.getX() - minPoint.getX(), maxPoint.getY() - minPoint.getY()));
    }

    @Override
    protected void _updateBoundingBox()
    {
        __boundingBox = new Rectangle();
        __boundingBox = __boundingBox.union(this.getGraphics().getBoundingBox());

        if (this.__parent != null)
        {
            __boundingBox.setX((int) (__boundingBox.getX() * this.getParent().getScaleX()));
            __boundingBox.setY((int) (__boundingBox.getY() * this.getParent().getScaleY()));
        }
        
        __boundingBox.setX(this.getAbsoluteX() + __boundingBox.getX());
        __boundingBox.setY(this.getAbsoluteY() + __boundingBox.getY());
        
        for (DisplayObject child : __children)
        {
            __boundingBox = __boundingBox.union(child.getBoundingBox());
        }

        __boundingBox.setWidth((int) (this.__scaleX * __boundingBox.getWidth()));
        __boundingBox.setHeight((int) (this.__scaleY * __boundingBox.getHeight()));

        if (this.__rotation != 0)
        {
            Rectangle newBounds = _findRotatedBounds(__boundingBox);
            __boundingBox = newBounds;
        }
    }

    public DisplayObject addChild(DisplayObject child)
    {
        if (child.getParent() != null)
        {
            child.getParent().removeChild(child);
        }

        if (!__children.contains(child))
        {
            __children.add(child);
            child.setStage(__stage);
            child.setParent(this);
            if (!child.hasMasker() && !child.isMasker())
            {
                child.setPainter(this.getPainter());
            }
        }

        return child;
    }

    public DisplayObject addChildAt(DisplayObject beforeChild, DisplayObject child) throws Exception
    {
        int index;

        if (!this.contains(beforeChild))
        {
            throw new Exception("The Parameter before child does not belong to this displaylist");
        }

        if (child.getParent() != null)
        {
            child.getParent().removeChild(child);
        }

        if (!__children.contains(child))
        {
            index = __children.indexOf(beforeChild);

            __before = new ArrayList<DisplayObject>();
            __after = new ArrayList<DisplayObject>();

            for (int i = 0; i < index; i++)
            {
                __before.add(__children.get(i));
            }
            for (int i = index; i < __children.size(); i++)
            {
                __after.add(__children.get(i));
            }
            __children.clear();
            __before.add(child);

            for (int i = 0; i < __before.size(); i++)
            {
                __children.add(__before.get(i));
            }

            for (int j = 0; j < __after.size(); j++)
            {
                __children.add(__after.get(j));
            }
            __before.clear();
            __after.clear();
            child.setParent(this);
            child.setStage(__stage);
            if (!child.hasMasker() && !child.isMasker())
            {
                child.setPainter(this.getPainter());
            }
        }

        return child;
    }

    public DisplayObject removeChild(DisplayObject child)
    {
        if (child.getParent() != null)
        {
            if (child.getParent() == this)
            {
                child.setStage(null);
                child.setParent(null);
                child.setPainter(null);
                __children.remove(child);
            }
            else
            {
                if (Debug.debug_enabled)
                {
                    Logger.log(this.getName() + " cannot remove the child " + child.getName() + " because it does not belong to its list");
                }
            }
        }
        else
        {
            if (Debug.debug_enabled)
            {
                Logger.log("The child is an orphan and does not belong to any display lists and hence there is no meaning to remove it from a list.");
            }
        }
        return child;
    }

    public DisplayObject getChildByName(String searchName)
    {
        for (DisplayObject child : __children)
        {
            if (child.getName().equals(searchName))
            {
                return child;
            }
        }
        return null;
    }

    public DisplayObject getChildByIndex(int index) throws IndexOutOfBoundsException
    {
        if (index > (__children.size() - 1))
        {
            throw new IndexOutOfBoundsException();
        }
        return __children.get(index);
    }

    public boolean contains(DisplayObject toSearch)
    {
        for (DisplayObject child : __children)
        {
            if (child == toSearch)
            {
                return true;
            }
        }
        return false;
    }

    @Override
    public void draw()
    {
        if (!__visible)
        {
            return;
        }

        if (__stage == null)
        {
            return;
        }

        if (this.__parent == null)
        {
            if (!this.__name.equals("_root"))
            {
                return;
            }
        }

        if (this.__painter == null)
        {
            return;
        }

        if (this.__hasMasker || this.__isMasker)
        {
            this.__painter.beginDraw();
        }

        //Then proceed to the painting of the object on the screen

        this.__painter.pushMatrix();

        this.__painter.translate(this.__x, this.__y);

        this.__painter.rotate(this.__radiansRotation);
        this.__painter.scale(__scaleX, __scaleY);

        if (this.__hasMasker || this.__isMasker)
        {
            this.__painter.background(0, 0, 0, 0);
        }

        this.__graphics.draw();

        if (__children.size() > 0)
        {
            for (DisplayObject child : __children)
            {
                if (!child.isMasker())
                {
                    child.draw();
                }
            }
        }

        //Update the bounding box first
        this._updateBoundingBox();

        this.__painter.popMatrix();

        super.draw();
    }

    @Override
    public void setPainter(PGraphics painter)
    {
        super.setPainter(painter);

        __graphics.setPainter(painter);

        for (DisplayObject child : __children)
        {
            if (!child.hasMasker() && !child.isMasker())
            {
                child.setPainter(painter);
            }
        }
    }

    public boolean isRoot()
    {
        return __isRoot;
    }

    public void setIsRoot(boolean isRoot)
    {
        this.__isRoot = isRoot;
    }

    public ArrayList<DisplayObject> getChildren()
    {
        return __children;
    }

    public Graphics getGraphics()
    {
        return __graphics;
    }

    @Override
    public void setStage(IStage stage)
    {
        super.setStage(stage);

        for (DisplayObject child : this.__children)
        {
            child.setStage(stage);
        }
    }

    @Override
    public int getWidth()
    {
        return this.getBoundingBox().getWidth();
    }

    @Override
    public int getHeight()
    {
        return this.getBoundingBox().getHeight();
    }
}
